#!/usr/bin/env python

import os
import sys
import re
import json
from collections import defaultdict
from pprint import pprint

from androguard.core.bytecodes import apk, dvm
from androguard.core.analysis.analysis import PathVar, TAINTED_PACKAGE_CALL
from androguard.core.analysis.analysis import uVMAnalysis, VMAnalysis
from androguard.decompiler.decompiler import DecompilerDAD

from jinja2 import Environment, FileSystemLoader


# converts a class name (java.lang.String) to the Java type (Ljava/lang/String;)
def classNameToJavaType(name):
	return 'L' + name.replace('.', '/') + ';'

# retrieves the receiver method from specified class
# className should be something like "java.lang.String"
def get_receiver_methods(dvmFormat, className):
	klass_obj = dvmFormat.get_class(classNameToJavaType(className))
	assert klass_obj is not None, "class not found: %s" % className

	methods = []
	for method in klass_obj.get_methods():
		#if not method.get_name().startswith('<'):
			methods.append(method)

	return methods

# retrieves reference counts for strings
# returns a dict of str-index -> ref-count
def get_code_strings(dvmFormat):
	strings = defaultdict(int)
	for method in dvmFormat.get_methods():
		for ins in method.get_instructions():
			if ins.get_name().startswith('const-string'):
				strings[ins.BBBB] += 1

	return strings


# ----------------------------------------------------------------------

a = apk.APK(sys.argv[1])
d = dvm.DalvikVMFormat(a.get_dex())
dx = uVMAnalysis(d)
decompiler = DecompilerDAD(d, dx)

env = Environment(loader=FileSystemLoader('.'))
env.filters['jsonify'] = json.dumps

# load report template
template = env.get_template('report-template.html')

template.globals['apk_filename'] = os.path.basename(sys.argv[1])

# ----------------------------------------------------------------------
# activities
activities = a.get_activities()
activities2 = {}
for klass in activities:
	filters = a.get_intent_filters("activity", klass)
	activities2[klass] = (filters, )

template.globals['activities'] = activities2

# ----------------------------------------------------------------------
# services
services = a.get_services()
template.globals['services'] = services

# ----------------------------------------------------------------------
# receivers
receivers = a.get_receivers()
receivers2 = {}
for klass in receivers:
	filters = a.get_intent_filters("receiver", klass)
	#print "\t", klass, filters or ""

	receiver_info = None
	class_src = []
	methods = get_receiver_methods(d, klass)
	if methods:
		for method in methods:
			method_src = decompiler.get_source_method(method) 
			method_src = method_src.strip('\r\n')
			method_src = re.sub('^\s{4}', '', method_src, 0, re.MULTILINE);
			class_src.append(method_src)
			#print method_src
	else:
		print 'unable to get receiver method'

	receivers2[klass] = (filters, class_src)

template.globals['receivers'] = receivers2

# ----------------------------------------------------------------------
# interesting strings?
strings = get_code_strings(d)
strings2 = {}
for str_idx in strings:
	the_string = d.get_cm_string(str_idx)
	if len(the_string.strip()) == 0:
		continue
	#print str_idx, strings[str_idx], repr(the_string)
	strings2[str_idx] = (strings[str_idx], repr(the_string))

template.globals['code_strings'] = strings2

# ----------------------------------------------------------------------
# find native libs?
files = a.get_files()
template.globals['native_libs'] = [f for f in files if f.startswith('lib/')]

# ----------------------------------------------------------------------
# permissions & code path

def path_data_to_obj(path_data):
	klass, method, signature = path_data
	klass = klass[1:-1].replace('/', '.')
	return {'class': klass, 'method': method, 'signature': signature}

# show code paths that execute APIs that require permissions
perm_callers = defaultdict(lambda: defaultdict(list))
for perm, paths in dx.get_permissions([]).iteritems():
	#print perm
	for path in paths:
		src = None
		dst = None
		if isinstance(path, PathVar):
			# PathVar just means "dst" is accessing "src" var
			# we normalize "src" to become the "accessor"
			src = path_data_to_obj(path.get_dst(d.get_class_manager()))
		else:
			src = path_data_to_obj(path.get_src(d.get_class_manager()))
			
			# if it's a call there would be a "dst" method
			if path.get_access_flag() == TAINTED_PACKAGE_CALL:
				dst = path_data_to_obj(path.get_dst(d.get_class_manager()))

		perm_callers[src['class']][src['method']].append({'src': src, 'dst': dst})
		#print json.dumps({'src': src, 'dst': dst})

# format the collected dict for translation to JSON
json_perm_callers = []
for klass in perm_callers:
	klass_caller = {'name': klass, 'children': []}
	for method in perm_callers[klass]:
		method_caller = {'name': method, 'num_children': len(perm_callers[klass][method])}
		klass_caller['children'].append(method_caller)

	json_perm_callers.append(klass_caller)

template.globals['permission_callers'] = json_perm_callers

template.globals['permissions'] = a.get_details_permissions()


# ----------------------------------------------------------------------
# write out the file
tpl_html = template.render()
#print tpl_html

with open('report.html', 'wb') as out:
	out.write(tpl_html)

